﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Util.ZPLPrinter
{
    // V 1.0.1 - 2022-08-19 17:22:42
    // 1 设置 SendStringToPrinter 过时, 搞清楚信息到打印机的原理, 主要是需要了解什么是 ANSI 动态编码
    // 2 新增方法 SendString2Printer 与 SendString2PrinterANSI

    /// <summary>
    /// ZPLHelper
    /// </summary>
    public class ZPLHelper
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        class DOCINFOA
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public string pDocName;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pOutputFile;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pDataType;
        }

        [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);

        [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        static extern bool ClosePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);

        [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        static extern bool EndDocPrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        static extern bool StartPagePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        static extern bool EndPagePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);

        // SendBytesToPrinter()
        // When the function is given a printer name and an unmanaged array
        // of bytes, the function sends those bytes to the print queue.
        // Returns true on success, false on failure.

        static bool sendBytesToPrinter(string docName, string szPrinterName, IntPtr pBytes, Int32 dwCount)
        {
            Int32 dwError = 0;
            Int32 dwWritten = 0;
            IntPtr hPrinter = new IntPtr(0);
            DOCINFOA docInfo = new DOCINFOA();
            bool bResult = false; // Assume failure unless you specifically succeed.

            docInfo.pDocName = docName;
            docInfo.pDataType = "RAW";

            if (ZPLHelper.OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
            {
                if (ZPLHelper.StartDocPrinter(hPrinter, 1, docInfo))
                {
                    if (ZPLHelper.StartPagePrinter(hPrinter))
                    {
                        bResult = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
                        ZPLHelper.EndPagePrinter(hPrinter);
                    }
                    ZPLHelper.EndDocPrinter(hPrinter);
                }
                ZPLHelper.ClosePrinter(hPrinter);
            }
            if (bResult == false)
            {
                dwError = Marshal.GetLastWin32Error(); // If you did not succeed, GetLastError may give more information about why not.
            }
            return bResult;
        }

        /// <summary>
        /// 发送文件到打印机
        /// </summary>
        /// <param name="docName">打印文档名称</param>
        /// <param name="szPrinterName">打印机名称</param>
        /// <param name="szFileName">文件路径</param>
        /// <returns></returns>
        public static bool SendFileToPrinter(string docName, string szPrinterName, string szFileName)
        {
            byte[] byteArr;
            using (FileStream fs = new FileStream(szFileName, FileMode.Open))
            {
                using (BinaryReader br = new BinaryReader(fs))
                {
                    byteArr = new byte[fs.Length];
                    byteArr = br.ReadBytes(byteArr.Length);
                }
            }

            bool bResult = false;
            IntPtr pUnmanagedBytes = new IntPtr(0);
            int nLength = byteArr.Length;
            pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
            Marshal.Copy(byteArr, 0, pUnmanagedBytes, nLength);
            bResult = ZPLHelper.sendBytesToPrinter(docName, szPrinterName, pUnmanagedBytes, nLength);
            Marshal.FreeCoTaskMem(pUnmanagedBytes); // Free the unmanaged memory that you allocated earlier.
            return bResult;
        }

        /// <summary>
        /// Send the converted ANSI string to the printer.
        /// 因为发送的是 ANSI, 所以打印中文需要指定字符集信息
        /// </summary>
        /// <param name="docName"></param>
        /// <param name="szPrinterName"></param>
        /// <param name="szString"></param>
        /// <returns></returns>
        [Obsolete("请改用 SendString2PrinterANSI")]
        public static bool SendStringToPrinter(string docName, string szPrinterName, string szString)
        {
            return SendString2PrinterANSI(docName, szPrinterName, szString);
        }

        /// <summary>
        /// Send the converted ANSI string to the printer.
        /// 因为发送的是 ANSI, 所以打印中文需要指定字符集信息
        /// <para>^XA</para>
        /// <para>^FX===== 设置字体为 V =====^FS</para>
        /// <para>^CWV,E:MSUNG24.FNT</para>
        /// <para>^FX===== 使用编码表 =====^FS</para>
        /// <para>^SEE:GB18030.dat</para>
        /// <para>^FX===== 设置编码CI26 包含 ASCII Transparency a 和 c 的多字节亚洲编码 =====^FS</para>
        /// <para>^CI26</para>
        /// <para>^LH0,0</para>
        /// <para>^FX===== 使用设置了V的字体 =====^FS</para>
        /// <para>^FO24,24^AVN,25,25 ^ FDI'm 中国人^FS</para>
        /// <para>^XZ</para>
        /// </summary>
        /// <param name="docName">打印文档名称</param>
        /// <param name="printerName">指定打印机名称</param>
        /// <param name="szString">ZPL II 指令</param>
        /// <returns></returns>
        public static bool SendString2PrinterANSI(string docName, string printerName, string szString)
        {
            IntPtr pBytes;
            Int32 dwCount;
            // How many characters are in the string?
            dwCount = StringHelper.CountANSILength(szString);
            // Assume that the printer is expecting ANSI text, and then convert
            // the string to ANSI text.
            pBytes = Marshal.StringToCoTaskMemAnsi(szString); // 关键 !!!!! 会将 String 转换成当前系统的 ANSI 编码
            // Send the converted ANSI string to the printer.
            ZPLHelper.sendBytesToPrinter(docName, printerName, pBytes, dwCount);
            Marshal.FreeCoTaskMem(pBytes);
            return true;
        }

        /// <summary>
        /// <para>使用指定的编码发送ZPL指令</para>
        /// <para>使用此方法可以无需放置字符集（GB2312, GB18080等）到打印机 或 指定字符集</para>
        /// <para>ZPLII 指令事例</para>
        /// 
        /// <para>^XA</para>
        /// <para>^CW1,E:MSHEI.FNT -- 指定打印机的中文字体</para>
        /// <para>^CI28            -- 更改国际字符集</para>
        /// <para>^LH0,0</para>
        /// <para>^FO20,20^A1N,25,25^FD大中華排名地下城(アテナ杯)開催決定！ ^FS^XZ"</para>
        /// </summary>
        /// <param name="docName">打印文档名称</param>
        /// <param name="printerName">指定打印机名称</param>
        /// <param name="szString">ZPL II 指令</param>
        /// <param name="encoding">默认值 UTF-8, 可空</param>
        /// <returns></returns>
        public static bool SendString2Printer(string docName, string printerName, string szString, Encoding encoding = null)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }

            byte[] byteArray = encoding.GetBytes(szString);
            IntPtr pUnmanagedBytes = new IntPtr(0);
            int nLength = byteArray.Length;
            pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
            Marshal.Copy(byteArray, 0, pUnmanagedBytes, nLength);
            bool bSuccess = ZPLHelper.sendBytesToPrinter(docName, printerName, pUnmanagedBytes, nLength);
            Marshal.FreeCoTaskMem(pUnmanagedBytes);
            return bSuccess;
        }
    }
}